package com.qianxun.spider.http;

import lombok.Data;
import lombok.experimental.Accessors;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author: huwei
 * @date: 2020/7/15 11:20
 * @version: 1.0.0
 */
public class DefaultHttpProxyPool implements HttpProxyPool{
    /**
     * 代理池
     */
    private ConcurrentLinkedQueue<HttpProxyWrapper> proxyPool;
    /**
     * 被标记失败的代理
     */
    private ConcurrentHashMap<String ,HttpProxy> failureProxyPool;
    /**
     * 重试次数
     *
     */
    private int retry;
    /**
     * 本地重试
     */
    private boolean localRetry;
    /**
     * 本地优先
     */
    private boolean localPriority;

    public DefaultHttpProxyPool (int retry ,boolean localRetry ,boolean localPriority){
        this.retry = retry;
        this.localRetry = localRetry;
        this.proxyPool = new ConcurrentLinkedQueue<>();
        this.failureProxyPool = new ConcurrentHashMap<>();
        this.localPriority = localPriority;
    }

    /**
     * 添加代理
     * @param proxy 代理服务信息
     * @param timeoutMills 代理服务有效时间（单位毫秒），当超过该时间将被自动移除代理池，小于等于0表示永不过期
     */
    public void addLast(HttpProxy proxy ,long timeoutMills){
        addLast(proxy, timeoutMills ,TimeUnit.MILLISECONDS);
    }

    /**
     * 添加代理
     * @param proxy 代理服务信息
     * @param timeout 代理服务有效时间
     * @param timeUnit 时间单位
     */
    public void addLast(HttpProxy proxy , long timeout , TimeUnit timeUnit){
        //失效代理池不存在时才加入，主要防止重复添加
        if(!this.failureProxyPool.containsKey(proxyKey(proxy))) {
            HttpProxyWrapper wrapper = new HttpProxyWrapper(proxy, timeUnit.toMillis(timeout));
            this.proxyPool.offer(wrapper);
        }
    }

    /**
     * 清除全部的代理数据
     */
    public void clear(){
        this.proxyPool.clear();
    }

    @Override
    public HttpProxy next() {
        HttpProxyWrapper wrapper = null;
        do{
            wrapper = this.proxyPool.poll();
        }while (wrapper != null && (wrapper.isTimeout() || failureProxyPool.remove(proxyKey(wrapper.getProxy())) != null));
        if(wrapper != null){
            //该ip未过期，添加到代理池末端，等待下次使用
            this.proxyPool.offer(wrapper);
        }
        return wrapper == null ? null : wrapper.getProxy();
    }

    @Override
    public int retry() {
        return this.retry;
    }

    @Override
    public boolean localRetry() {
        return this.localRetry;
    }

    @Override
    public boolean localPriority() {
        return this.localPriority;
    }

    @Override
    public void makeFailure(HttpProxy httpProxy) {
        this.failureProxyPool.put(proxyKey(httpProxy) ,httpProxy);
    }

    @Override
    public boolean isEmpty() {
        return size() <= 0 ;
    }

    @Override
    public int size() {
        return this.proxyPool.size();
    }

    private String proxyKey(HttpProxy httpProxy){
        return httpProxy.getHost() + "#" + httpProxy.getPort();
    }

    @Data
    @Accessors(chain = true)
    private class HttpProxyWrapper {
        HttpProxy proxy;
        /**
         * 加入时间
         */
        long initTimeMillis;
        /**
         * 有效时间，当该ip超过该时间时，将作废并剔除；
         */
        long timeoutMillis;

        public HttpProxyWrapper(HttpProxy proxy, long timeoutMillis) {
            this.proxy = proxy;
            this.timeoutMillis = timeoutMillis;
            this.initTimeMillis = System.currentTimeMillis();
        }

        boolean isTimeout(){
            return this.timeoutMillis > 0 && (this.timeoutMillis + this.initTimeMillis) < System.currentTimeMillis();
        }
    }
}
